package xiaojian.toolkit.bean.utils;

import com.google.common.primitives.Bytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import xiaojian.toolkit.bean.bytebean.core.BeanFieldCodec;
import xiaojian.toolkit.bean.tlv.TLVSignal;
import xiaojian.toolkit.bean.tlv.annotation.TLVAttribute;
import xiaojian.toolkit.bean.tlv.encode.TLVEncodeContext;
import xiaojian.toolkit.bean.tlv.encode.TLVEncoderOfBean;
import xiaojian.toolkit.bean.xip.annotation.SsipSignal;
import xiaojian.toolkit.bean.xip.core.XipSignal;
import xiaojian.toolkit.transport.protocol.xip.XipHeader;
import xiaojian.toolkit.util.ByteUtils;

import java.util.List;
import java.util.UUID;

public class CodecUtils {
	private static final Logger logger = LoggerFactory
			.getLogger(CodecUtils.class);

	private int dumpBytes = 256;

	private BeanFieldCodec xipBeanCodec;
	private TLVEncoderOfBean tlvBeanEncoder;

	public byte[] encode(Object msg) throws Exception {
		if (msg instanceof XipSignal) {
			SsipSignal signal = msg.getClass().getAnnotation(SsipSignal.class);
			if (signal != null) {
				return encodeSsip((XipSignal) msg, signal);
			}
			throw new RuntimeException(
					"invalid saip/ssip signal, bcs of no messageCode.");
		} else if (msg instanceof TLVSignal) {
			return encodeTLV((TLVSignal) msg);
		} else {
			throw new RuntimeException("encode: bean " + msg
					+ " is not neigher XipSignal nor TLVSignal.");
		}
	}

	private byte[] encodeSsip(XipSignal signal, SsipSignal attr)
			throws Exception {
		// once
		byte[] bytesBody = xipBeanCodec.encode(xipBeanCodec
				.getEncContextFactory().createEncContext(signal,
						signal.getClass(), null));

		XipHeader header = createHeader((byte) 1, signal.getIdentification(),
				attr.messageCode(), bytesBody.length,
				XipHeader.SSIP_HEADER_LENGTH);

		header.setTypeForClass(signal.getClass());

		byte[] bytes = Bytes.concat(xipBeanCodec.encode(xipBeanCodec
				.getEncContextFactory().createEncContext(header,
						XipHeader.class, null)), bytesBody);

		if (logger.isDebugEnabled()) {
			logger.debug("encode XipMessage:" + signal);
			logger.debug("and XipMessage raw bytes -->");
			logger.debug(ByteUtils.bytesAsHexString(bytes, dumpBytes));
		}

		return bytes;
	}

	private byte[] encodeTLV(TLVSignal signal) throws Exception {
		TLVEncodeContext ctx = tlvBeanEncoder.getEncodeContextFactory()
				.createEncodeContext(signal.getClass(), null);
		List<byte[]> byteList = tlvBeanEncoder.encode(signal, ctx);

		TLVAttribute attr = signal.getClass().getAnnotation(TLVAttribute.class);
		if (null == attr) {
			throw new RuntimeException(
					"invalid top tlv object, missing @TLVAttribute.");
		}

		final byte[] bytesBody = ByteUtils.union(byteList);
		final int headerSize = xipBeanCodec.getStaticByteSize(XipHeader.class);
		final XipHeader header = createHeader((byte) 2,
				signal.getIdentification(), attr.tag(), bytesBody.length,
				headerSize);// Tlv的basicVer=2，头类型可以为ssip，也可以为saip，因此头长度必须计算出来

		header.setSourceId(signal.getSourceId());
		header.setTypeForClass(signal.getClass());

		final byte[] bytes = Bytes.concat(xipBeanCodec.encode(xipBeanCodec
				.getEncContextFactory().createEncContext(header,
						XipHeader.class, null)), bytesBody);

		if (logger.isDebugEnabled()) {
			logger.debug("encode TLV: {} with headerSize {}", signal,
					headerSize);
			logger.debug("and TLV raw bytes -->");
			logger.debug(ByteUtils.bytesAsHexString(bytes, dumpBytes));
		}

		return bytes;
	}

	private XipHeader createHeader(byte basicVer, UUID id, int messageCode,
			int messageLen, int headerSize) {
		final XipHeader header = new XipHeader();
		header.setTransaction(id);
		header.setLength(headerSize + messageLen);
		header.setMessageLength(messageLen);
		header.setMessageCode(messageCode);
		header.setBasicVer(basicVer);

		return header;
	}

	public byte[] genHeaderToBytes(byte basicVer, UUID id, int messageCode,
			int messageLen) {

		// 指定 basicVer 为 != 1 != 2 的其它版本
		final XipHeader header = createHeader(basicVer, id, messageCode,
				messageLen, xipBeanCodec.getStaticByteSize(XipHeader.class));

		return xipBeanCodec.encode(xipBeanCodec.getEncContextFactory()
				.createEncContext(header, XipHeader.class, null));

	}

	public void setXipBeanCodec(BeanFieldCodec xipBeanCodec) {
		this.xipBeanCodec = xipBeanCodec;
	}

	public int getDumpBytes() {
		return dumpBytes;
	}

	public void setDumpBytes(int dumpBytes) {
		this.dumpBytes = dumpBytes;
	}

	public void setTlvBeanEncoder(TLVEncoderOfBean tlvBeanEncoder) {
		this.tlvBeanEncoder = tlvBeanEncoder;
	}
}
